package core

import (
	"fmt"
	"log"

	"github.com/boltdb/bolt"
)

const dbFile = "blockchain.db"

//用名字是blocks的桶替代数组
const blocksBucket = "blocks"

/*
其实相当于创建了个表，表里面像map一样查询，key是区块hash,value是区块数据
*/

// type BlockChain struct {
// 	Blocks []*Block
// }

//BlockChain 区块链结构体，这里就只存放末尾区块hash和数据库链接
//区块链里区块数据都存放在数据库里
type BlockChain struct {
	tip []byte   //末尾hash
	Db  *bolt.DB //数据库连接
}

//区块链像章鱼一样(也可以理解为头发分叉)，同一个创世区块作为头，tip标识不同的分支区块链
//最长的那条链被认为是主分支

//BlockchainIterator 区块链迭代器，用这个来读取每个区块
type BlockchainIterator struct {
	currentHash []byte   //当前hash
	db          *bolt.DB //数据库连接，因为只在包里面用所以没有大写
}

//Iterator 使用区块链生成迭代器
func (bc *BlockChain) Iterator() *BlockchainIterator {
	//迭代器从末尾区块开始，所以初始hash为末尾hash
	bci := &BlockchainIterator{bc.tip, bc.Db}
	return bci
}

//Next 获取迭代器当前对应的区块并移动迭代器到上一个区块
func (i *BlockchainIterator) Next() *Block {
	//创建个区块结构体来装查到的区块
	var block *Block

	//查询区块
	err := i.db.View(func(tx *bolt.Tx) error {
		//查询桶
		b := tx.Bucket([]byte(blocksBucket))
		//根据hash获取区块数据
		decodeBlock := b.Get(i.currentHash)
		//解码数据
		block = DeserializeBlock(decodeBlock)

		return nil
	})
	if err != nil {
		log.Panic(err)
	}
	//迭代器移动到上一个区块，hash变成了上一个区块的hash
	i.currentHash = block.PrevBlockHash

	return block
}

// //AddBlock 往区块链中添加区块
// func (bc *BlockChain) AddBlock(data string) {
// 	//获取末尾区块
// 	prevblock := bc.Blocks[len(bc.Blocks)-1]
// 	//将数据和末尾区块的hash传给NewBlock用于创建新区块
// 	newblock := NewBlock(data, prevblock.Hash)
// 	//最后将新区块放入区块链内
// 	bc.Blocks = append(bc.Blocks, newblock)
// }

//AddBlock 往区块链中添加区块
func (bc *BlockChain) AddBlock(data string) {
	//先获取上一个的hash
	var lastHash []byte

	//查询数据库获取末尾hash，View是只读
	err := bc.Db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(blocksBucket))
		lastHash = b.Get([]byte("l"))

		return nil
	})

	//创建一个新区块
	newBlock := NewBlock(data, lastHash)
	//将区块加入数据库中
	err = bc.Db.Update(func(tx *bolt.Tx) error {
		//查询桶
		b := tx.Bucket([]byte(blocksBucket))
		//添加区块
		err := b.Put(newBlock.Hash, newBlock.Serialize())
		if err != nil {
			log.Panic(err)
		}
		//更新末尾hash
		err = b.Put([]byte("l"), newBlock.Hash)
		if err != nil {
			log.Panic(err)
		}
		//末尾hash重新赋值给tip
		bc.tip = newBlock.Hash

		return nil
	})
	if err != nil {
		log.Panic(err)
	}
}

// //NewBlockchain 创建一个新区块链
// func NewBlockchain() *BlockChain {
// 	//返回一个只包含了创世块的的区块链
// 	return &BlockChain{[]*Block{NewGenesisBlock()}}
// }

//NewBlockchain 创建一个新区块链
func NewBlockchain() *BlockChain {
	//创建一个变量来存末尾区块的hash
	var tip []byte
	//打开数据库
	db, err := bolt.Open(dbFile, 0600, nil)
	if err != nil {
		log.Panic(err)
	}

	//更新数据库
	err = db.Update(func(tx *bolt.Tx) error {
		//这里去数据库里找桶，个人认为相当于根据名字找表，blocksBucket是自己定的常量
		//根据名字找桶，blocksBucket常量的值是blocks
		b := tx.Bucket([]byte(blocksBucket))
		//如果不存在桶，就创建一个，并设置初始位置
		if b == nil {
			fmt.Println("找不到数据库，正在创建一个新的···")
			//先创建一个创世纪块
			genesis := NewGenesisBlock()

			//创建一个桶，名字是blocksBucket的值
			b, err := tx.CreateBucket([]byte(blocksBucket))
			if err != nil {
				log.Panic(err)
			}
			//添加数据，key是hash，value是区块数据
			err = b.Put(genesis.Hash, genesis.Serialize())
			if err != nil {
				log.Panic(err)
			}
			//设置一个类似指针一样的东西，存储末尾区块的hash
			//这样可以通过这个值找到末尾区块，最终可找到所有区块
			err = b.Put([]byte("l"), genesis.Hash)
			if err != nil {
				log.Panic(err)
			}
			//把末尾区块hash给tip，标记末尾hash
			tip = genesis.Hash
		} else {
			//如果存在桶就直接把末尾hash拿出来
			tip = b.Get([]byte("l"))
		}
		return nil
	})

	if err != nil {
		log.Panic(err)
	}

	//最终组成区块链结构体返回
	bc := BlockChain{tip, db}

	//返回一个只包含了创世块的的区块链
	return &bc
}
